%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     角色管理器
%%% @end
%%%-------------------------------------------------------------------
-module(role_mgr).

-behaviour(gen_server).

-export([
    get_role_base/2
    ,create_role/5
    ,save/1
    ,reload_base/0
    ,get_id/0
    ,is_exist/1
    ,alter_name/2
    ,load_base/0
    ,load_base2/0
]).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-include("common.hrl").
-include("role.hrl").
-include("role_data.hrl").

-record(state, {reg_next_id = 0}).


%%%===================================================================
%%% API functions
%%%===================================================================

%% @spec get_role_base(Type, Val) -> {ok, L} | {false, Reason}
%% 根据类型，获取角色基础数据
%% 详情请见  role_dao:fetch_base/2
get_role_base(Type, Val) ->
    ?CALL(?MODULE, {get_role_base, Type, Val}).

%% @spec create_role(Account, Name, Sex, RegIp) -> {ok, Rid} | {false, Reason}
%% 创建用户
%% 详情请见  role_dao:create_role/5
create_role(Account, Sid, Name, Sex, RegIp) ->
    ?CALL(?MODULE, {create_role, Account, Sid, Name, Sex, RegIp}).

get_id() ->
    ?CALL(?MODULE, get_id).

%% 保存角色数据
save(Role) ->
    role_dao:save_role(Role),
    ok.

%% @spec reload_base() 
%% 重加载role_base
reload_base() ->
    ?MODULE ! {reload_base}.

%% @spec is_exist(Pat) -> true | false
%% Pat ->  eg:[{#role_base{id = '$1', name = '$2', _ = '_'}, [{'andalso', {'=:=', '$1', 100}, {'=:=', '$2', <<>>}}], ['$_']}].
%% 查询玩家是否已存在
is_exist(Pat) ->
    ?CALL(?MODULE, {is_exist, Pat}).

%% 改名
alter_name(Role, Name) ->
    {ok, Base} = role_base_parse:to_role_base(Role),
    ?CALL(?MODULE, {alter_name, Name, Base}).

%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    process_flag(trap_exit, true),
    case load_base2() of
        ok ->
            RegNextId =
                case db_mnesia_info:get(role_reg_next_id) of
                    Nid when is_integer(Nid) ->
                        <<_:32,Max:32>> = <<Nid:64>>,
                        Max;
                    _ -> 1
                end,
            {ok, #state{reg_next_id = RegNextId}};
        {error, Reason} ->
            {stop, Reason}
    end.

handle_call(get_id, _From, State = #state{reg_next_id = Rid}) ->
    {reply, Rid, State#state{reg_next_id = Rid + 1}};

%% 获取角色基础数据
handle_call({get_role_base, Type, Val}, _From, State) ->
    case catch role_dao:fetch_base(Type, Val) of
        {ok, L} ->
            {reply, {ok, L}, State};
        {false, Reason} ->
            {reply, {error, Reason}, State};
        Other ->
            ?ERR("获取角色基础数据出错: ~w", [Other]),
            {reply, {error, err_get_role_base}, State}
    end;

%% 创建用户
handle_call({create_role, Account, Sid, Name, Sex, RegIp}, _From, State = #state{reg_next_id = NextId}) ->
    case check_repeat(Account, Sid, Name) of
        false ->
            {reply, {error, err_role_create_repeat}, State};
        _ ->
            <<Rid:64>> = <<Sid:32,NextId:32>>,
            case catch role_dao:create_role(Rid, Sid, Account, Name, Sex, RegIp) of
                ok ->
                    NextIdNew = NextId + 1,
                    db_mnesia_info:set(role_reg_next_id, NextIdNew),
                    {reply, {ok, Rid}, State#state{reg_next_id = NextIdNew}};
                {false, Reason} ->
                    {reply, {error, Reason}, State};
                Other ->
                    ?ERR("创建角色出错: ~w", [Other]),
                    {reply, {error, err_create_role}, State}
            end
    end;

%% 判断用户是否存在
handle_call({is_exist, Pat}, _From, State) ->
    case catch db_mnesia:select(role_base, Pat) of
        {ok, [_ | _]} -> {reply, true, State};
        _ -> {reply, false, State}
    end;

%% 改名
handle_call({alter_name, Name, RoleBase}, _From, State) ->
    Pat = [{#role_base{name = '$1', _ = '_'}, [{'=:=', '$1', Name}], ['$_']}],
    Reply = case catch db_mnesia:select(role_base, Pat) of
                {ok, []} ->
                    db_mnesia:write(role_base, RoleBase#role_base{name = Name, save_time = util_time:unixtime()}),
                    true;
                _ ->
                    false
            end,
    {reply, Reply, State};

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.


handle_cast(_Msg, State) ->
    {noreply, State}.


handle_info({reload_base}, State) ->
    load_base2(),
    {noreply, State};

handle_info(_Info, State) ->
    {noreply, State}.


terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%%===================================================================
%%% Internal functions
%%%===================================================================

%% 从role_data中读取所有base数据
load_base() ->
    Start = util_time:unixtime(),
    FunFold = fun
                  (RoleData, AccIn) ->
                      RoleBase = erlang:element(4, RoleData),
                      case role_base_parse:parse(RoleBase) of
                          {ok, NewRoleBase} ->
                              case mnesia:write(role_base, NewRoleBase, write) of
                                  ok ->
                                      AccIn;
                                  Other ->
                                      ?ERR("写入role_base失败: ~w", [Other]),
                                      error
                              end;
                          {false, _Reason} ->
                              error
                      end
              end,
    Fun = fun() -> mnesia:foldl(FunFold, [], role_data) end,
    case mnesia:transaction(Fun) of
        {atomic, _Result} ->
            LoadEnd = util_time:unixtime(),
            ?INFO("角色基础数据加载完成，使用了~w秒, 共~w条记录", [LoadEnd - Start, mnesia:table_info(role_base, size)]),
            ok;
        {aborted, Reason} ->
            ?ERR("角色基础数据加载出错: ~w", [Reason]),
            {error, Reason}
    end.

%% 从 role_data 中读取所有 base 数据
load_base2() ->
    LoadStart = util_time:unixtime(),
    erlang:put(lev_stat, []),

    FunFold = fun
                  (RoleData = #role_data{rid = Rid}, AccIn) ->
                      RoleBase = erlang:element(#role_data.base, RoleData),
                      case role_base_parse:parse(RoleBase) of
                          {ok, NewRoleBase = #role_base{lev = Lev, rid = Rid}} ->
                              case mnesia:dirty_write(role_base, NewRoleBase) of
                                  ok ->
                                      stat_lev(Lev),
                                      AccIn;
                                  Other ->
                                      ?ERR("写入 role_base 失败:~w", [Other]),
                                      error
                              end;
                          {ok, #role_base{rid = Rid2}} ->
                              ?ERR("id不一致 role_data id:~w, role_base id：~w",[Rid, Rid2]);
                          {false, _Reason} ->
                              ?ERR("parse error:~w",[_Reason]),
                              error
                      end
              end,

    case db_mnesia:foldl(FunFold, role_data) of
        ok ->
            LoadEnd = util_time:unixtime(),
            ?INFO("角色基础数据加载完成, 用了 ~w 秒, 共 ~w 条记录", [LoadEnd - LoadStart, mnesia:table_info(role_base, size)]),
            ?INFO("角色等级分布:~w", [erlang:get(lev_stat)]),
            ok;
        {error, Reason} ->
            ?ERR("角色基础数据加载出错:~w", [Reason]),
            {error, Reason}
    end.

check_repeat(Account, Sid, Name) ->
    case db_mnesia:read(role_base, #role_base.name, Name) of
        {ok, [_|_]} -> false;
        {ok, []} ->
            case db_mnesia:read(role_base, #role_base.account, Account) of
                {ok, []} -> true;
                {ok, RoleBases} ->
                    not lists:any(fun(Item) -> Item#role_base.sid =:= Sid end, RoleBases)
            end
    end.

stat_lev(Lev) ->
    case erlang:get(lev_stat) of
        L when is_list(L) ->
            case lists:keytake(Lev, 1, L) of
                {value, {_, Count}, Left} ->
                    erlang:put(lev_stat, [{Lev, Count + 1} | Left]);
                false ->
                    erlang:put(lev_stat, [{Lev, 1} | L])
            end;
        _ ->
            erlang:put(lev_stat, [{Lev, 1}])
    end.
